/**********************************************************************//**
 @file draw.cpp 
 @brief Contains methods for drawing objects in the canvas
 @author Ricardo C. Marques <rmarques@inf.puc-rio.br>
 @version 0.0.1
 @date 05/11/2012
**************************************************************************/

// Standard Libraries
#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#undef   _CRT_SECURE_NO_WARNINGS

// Graphic Libraries
#define GLUT_DISABLE_ATEXIT_HACK
#include "GL/glut.h"
#undef  GLUT_DISABLE_ATEXIT_HACK

// Module Own Header File
#include "draw.hpp"

// Constants
#define SUBDIV           20 // Grid Discretization

// Static Variables Declaration
/**************************************************************************/

// Cartesian Plane Domain ( in world coordinates )
static float xmin=-1.;
static float xmax= 1.;
static float ymin=-1.;
static float ymax= 1.;

// Function for Drawing the Model and Special Events
static DrawFunc DrawModel = NULL;
static DrawFunc DrawEvent = NULL;

// Display Configurations
enum DisplayMode
{
    BACK  = -1,
    OFF   =  0,
    FRONT =  1
};
static DisplayMode AxisDisplayMode = BACK;
static DisplayMode GridDisplayMode = BACK;

// Function Definitions
/**************************************************************************/
// Convert Raster and World Coordinates
void RasterToWorld( float  rasterX, float  rasterY, 
                    float*  worldX, float*  worldY  )
{
    *worldX = xmin+(rasterX/glutGet(GLUT_WINDOW_WIDTH ))*(xmax-xmin);
    *worldY = ymax+(rasterY/glutGet(GLUT_WINDOW_HEIGHT))*(ymin-ymax);
}
void WorldToRaster( float   worldX, float   worldY,
                    float* rasterX, float* rasterY   )
{
    *rasterX = (worldX-xmin)/(xmax-xmin)*glutGet(GLUT_WINDOW_WIDTH );
    *rasterY = (worldY-ymin)/(ymax-ymin)*glutGet(GLUT_WINDOW_HEIGHT);
}

// Set and Get Visualization Window (in world coordinates)
void GetVisualizationWindow( float* pxmin, float* pxmax, 
                             float* pymin, float* pymax   )
{
    *pxmin = xmin;
    *pxmax = xmax;
    *pymin = ymin;
    *pymax = ymax;
}
void SetVisualizationWindow( float  pxmin, float  pxmax, 
                             float  pymin, float  pymax   )
{
    xmin = pxmin;
    xmax = pxmax;
    ymin = pymin;
    ymax = pymax;

    glLoadIdentity();
    gluOrtho2D(xmin, xmax, ymin, ymax);
}
// Set function responsible for drawing the model and special events
void SetDrawModelFunc(DrawFunc func)
{
    DrawModel = func;
}
void SetDrawEventFunc(DrawFunc func)
{
    DrawEvent = func;
}
// Auxiliary Drawing Functions
void DrawPoints(float* points, size_t num_points, float pointsize, float r, float g, float b)
{
    glPointSize(pointsize);
    glColor3f(r,g,b);
    glBegin(GL_POINTS);
    for(size_t i=0; i<num_points; i++)
        glVertex2f(points[2*i], points[2*i+1]);
    glEnd();
    glPointSize(1.);
}
void DrawWirePolygon (float* vertices, size_t num_vertices, float linewidth, float r, float g, float b)
{
    glLineWidth(linewidth);
    glColor3f(r,g,b);
    glBegin(GL_LINE_LOOP);
    for(size_t i=0; i<num_vertices; i++)
        glVertex2f(vertices[2*i], vertices[2*i+1]);
    glEnd();
    glLineWidth(1.0);
}
void DrawSolidPolygon(float* vertices, size_t num_vertices, float r, float g, float b)
{
    glColor3f(r,g,b);
    glBegin(GL_POLYGON); 
    for(size_t i=0; i<num_vertices; i++)
        glVertex2f(vertices[2*i], vertices[2*i+1]);
    glEnd();
}
void DrawWireMesh (float* nodes, unsigned int* triangles, size_t num_triangles, 
                   float linewidth, float edge_r, float edge_g, float edge_b)
{
    glLineWidth(linewidth);
    glColor3f(edge_r,edge_g,edge_b);
    glBegin(GL_LINES);
    for(size_t i=0; i<num_triangles; i++)
    {
        unsigned int v1 = triangles[3*i  ];
        unsigned int v2 = triangles[3*i+1];
        unsigned int v3 = triangles[3*i+2];

        glVertex2f(nodes[2*v1], nodes[2*v1+1]);
        glVertex2f(nodes[2*v2], nodes[2*v2+1]);

        glVertex2f(nodes[2*v2], nodes[2*v2+1]);
        glVertex2f(nodes[2*v3], nodes[2*v3+1]);

        glVertex2f(nodes[2*v3], nodes[2*v3+1]);
        glVertex2f(nodes[2*v1], nodes[2*v1+1]);
    }
    glEnd();
    glLineWidth(1.0);

}
void DrawSolidMesh( float* nodes, unsigned int* triangles, size_t num_triangles, 
                    float faces_r, float faces_g, float faces_b )
{
    glColor3f(faces_r,faces_g,faces_b);
    glBegin(GL_TRIANGLES);
    for(size_t i=0; i<num_triangles; i++)
    {
        unsigned int v1 = triangles[3*i  ];
        unsigned int v2 = triangles[3*i+1];
        unsigned int v3 = triangles[3*i+2];
        glVertex2f(nodes[2*v1], nodes[2*v1+1]);
        glVertex2f(nodes[2*v2], nodes[2*v2+1]);
        glVertex2f(nodes[2*v3], nodes[2*v3+1]);
    }
    glEnd();
}

// Display Callback function
static void DrawNumber(float worldX, float worldY, float number)
{
    glRasterPos2d(worldX, worldY);

    char strNumber[11];
    if(number>1e+5)
        strcpy(strNumber, "+Infinity");
    else if(number<-1e+5)
        strcpy(strNumber, "-Infinity");
    else
        sprintf (strNumber, "%7.3f", number);

    size_t k=0;
    while(strNumber[k]!='\0')
        glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, strNumber[k++] );
}
static void DrawAxis (void)
{
    glColor3f(1.f,1.f,1.f); // cor branco
    glBegin(GL_LINES);

    glVertex2f(xmax,0); //eixo x
    glVertex2f(xmin,0);

    glVertex2f(0,ymin); //eixo y
    glVertex2f(0,ymax);

    glEnd();

    DrawNumber(0.8*xmax,0.,xmax);
    DrawNumber(xmin,0.,xmin);
    DrawNumber(0.,0.95*ymax,ymax);
    DrawNumber(0.,ymin,ymin);
}

static void DrawGrid (void)
{
    float dx=(float)(xmax-xmin)/SUBDIV;
    float dy=(float)(ymax-ymin)/SUBDIV;

    glColor3f(0.f,0.f,1.f);
    glLineStipple(2, 0xAAAA);
    glEnable(GL_LINE_STIPPLE);
    glBegin(GL_LINES);

    for(float x=0; x>xmin; x-=dx)
    {
        glVertex2f(x,ymin);
        glVertex2f(x,ymax);
    }

    for(float x=0; x<xmax; x+=dx)
    {
        glVertex2f(x,ymin);
        glVertex2f(x,ymax);
    }

    for(float y=0; y>ymin; y-=dy)
    {
        glVertex2f(xmin,y);
        glVertex2f(xmax,y);
    }

    for(float y=0; y<ymax; y+=dy)
    {
        glVertex2f(xmin,y);
        glVertex2f(xmax,y);
    }

    glEnd();
    glDisable(GL_LINE_STIPPLE);

}


void DisplayCanvas(void)
{
    if(GridDisplayMode==BACK)
        DrawGrid();
    if(AxisDisplayMode==BACK)
        DrawAxis();

    if(DrawModel!=NULL)
        DrawModel();

    if(GridDisplayMode==FRONT)
        DrawGrid();
    if(AxisDisplayMode==FRONT)
        DrawAxis();

    if(DrawEvent!=NULL)
        DrawEvent();
}
// Set Display Options
static void ChangeDisplayMode(DisplayMode* dsp_mode)
{
    if(*dsp_mode==BACK)
        *dsp_mode=OFF;
    else if(*dsp_mode==OFF)
        *dsp_mode=FRONT;
    else // *dsp_mode==FRONT
        *dsp_mode=BACK;
}
void ChangeAxisDisplayMode()
{
    ChangeDisplayMode(&AxisDisplayMode);
}
void ChangeGridDisplayMode()
{
    ChangeDisplayMode(&GridDisplayMode);
}